﻿using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Common;
using System.Data.EntityClient;
using System.Linq;
using System.Text;
using Fintech.EntityFrameworkEngine;
using Fintech.Utils;
using ZOVNP.Model.Abstracts;

namespace ZOVNP.Engine
{
    /// <summary> Загрузчик движка, работающего с базой данных Skif_BP. </summary>
    public class ZOVNPEngineLoader : EFEngineLoader
    {
        #region Типы

        /// <summary> Специализированный адаптер для связывания работы движка Скиф-БП со спецификой SQL ADO.NET провайдера. </summary>
        private sealed class SkifBPSqlProviderAdapter : SqlProviderAdapter
        {
            #region Поля

            private const string validation = "select count(*) as [Schema_Count] from [INFORMATION_SCHEMA].[SCHEMATA] where [SCHEMA_NAME] in ('nsi','meta','head','global','lock','access','plugin','audit','analytics','docflow')";

            #endregion

            #region Конструкторы

            internal SkifBPSqlProviderAdapter(System.Data.SqlClient.SqlClientFactory Factory) : base(Factory) { }

            #endregion

            #region Методы

            /// <summary> Создание специального исключения, связанного с проблемами используемого при подключении пароля. </summary>
            /// <param name="Error"> Исходное возникшее при попытке подключения к источнику данных исключение. </param>
            /// <param name="ConnectionString"> Строка соединения с источником данных. </param>
            /// <returns> Специализированное исключение либо null при отсутствии такового. </returns>
            /// <remarks> Если этот метод не предполагает возврат специально определенного исключения, то оснасткой будет выброшено исходное исключение. <paramref name="Error"/>. </remarks>
            public override Exception CreateSpecializedPasswordException(Exception Error, string ConnectionString)
            {
                var deepest = Error.GetDeepestException();
                var user = GetUser(ConnectionString);

                if (IsPasswordExpired(deepest))
                    return new PasswordException(string.Format("Для пользователя {0} истек срок действия текущего пароля.", user), Error);
                if (IsPasswordMustBeReset(deepest))
                    return new PasswordException(string.Format("Пользователю {0} необходимо обновить текущий пароль.", user), Error);

                var casted = Error as System.Data.SqlClient.SqlException;
                if (casted == null) return null;
                switch (casted.Number)
                {
                    case 15113: return new PasswordException(string.Format("Ошибка имени входа для пользователя {0}. Причина: Ошибка проверки пароля. Учетная запись заблокирована.", user), Error);
                    case 18463: return new PasswordException(string.Format("Ошибка имени входа для пользователя {0}. Причина: ошибка изменения пароля. В данный момент этот пароль не может быть использован.", user), Error);
                    case 18464: return new PasswordException(string.Format("Ошибка имени входа для пользователя {0}. Причина: ошибка изменения пароля. Пароль слишком короток и не отвечает требованиям политики.", user), Error);
                    case 18465: return new PasswordException(string.Format("Ошибка имени входа для пользователя {0}. Причина: ошибка изменения пароля. Пароль слишком длинный и не отвечает требованиям политики.", user), Error);
                    case 18466: return new PasswordException(string.Format("Ошибка имени входа для пользователя {0}. Причина: ошибка изменения пароля. Пароль недостаточно сложен и не отвечает требованиям политики.", user), Error);
                    case 18467: return new PasswordException(string.Format("Ошибка имени входа для пользователя {0}. Причина: ошибка изменения пароля. Пароль не отвечает требованиям динамической библиотеки фильтрации паролей.", user), Error);
                    case 18468: return new PasswordException(string.Format("Ошибка имени входа для пользователя {0}. Причина: ошибка изменения пароля. В процессе проверки пароля произошла непредвиденная ошибка.", user), Error);
                    default: return null;
                }
            }

            /// <summary> Создание специальной команды к источнику данных. </summary>
            /// <param name="CommandKey"> Ключ команды, определяющий требуемую семантику команды. </param>
            /// <returns> Объект типа <c>DbCommand</c>, служащий для выполнения команды. </returns>
            public override DbCommand CreateSpecialCommand(string CommandKey)
            {
                var connection = Engine.EntityConnection.StoreConnection;
                DbCommand command = null;

                switch (CommandKey)
                {
                    case CommandKeys.DATABASE_VALIDATION:
                        command = connection.CreateCommand();
                        command.CommandText = validation;
                        command.CommandType = CommandType.Text;
                        break;
                    case CommandKeys.CURRENT_USER:
                        command = connection.CreateCommand();
                        command.CommandText = "[access].[p_Get_User]";
                        command.CommandType = CommandType.StoredProcedure;
                        break;
                    case CommandKeys.CURRENT_USER_WORKPLACES:
                        command = connection.CreateCommand();
                        command.CommandText = "[access].[p_Get_User_WorkPlaces]";
                        command.CommandType = CommandType.StoredProcedure;
                        break;
                }

                if (command != null) return command;
                throw new NotSupportedException(CommandKey);
            }

            #endregion
        }

        #endregion

        #region Конструкторы

        /// <summary> Создать загрузчик движка. </summary>
        public ZOVNPEngineLoader() : base() { }

        #endregion

        #region Методы

        /// <summary> Создание адаптера для связывания движка со спецификой используемого ADO.NET провайдера. </summary>
        /// <param name="Factory"> Фабрика используемого ADO.NET провайдера, на основе которого происходит работа с источником данных. </param>
        /// <returns> Адаптер для используемого ADO.NET провайдера. </returns>
        protected override DbProviderAdapter MakeAdapter(DbProviderFactory Factory)
        {
            Factory.CheckOnNull("Factory");
            if (Factory is System.Data.SqlClient.SqlClientFactory) return new SkifBPSqlProviderAdapter((System.Data.SqlClient.SqlClientFactory)Factory);
            return base.MakeAdapter(Factory);
        }

        /// <summary> Создание соединения с источником данных. </summary>
        /// <param name="Adapter"> Используемый адаптер ADO.NET провайдера, на основе которого происходит работа с источником данных. </param>
        /// <param name="ConnectionString"> Строка соединения с источником данных. </param>
        /// <returns> Объект соединения с источником данных. </returns>
        protected override DbConnection MakeConnection(DbProviderAdapter Adapter, string ConnectionString)
        {
#if DEBUG
            return ConnectionTuner.Instance.TuneConnection(ConnectionString, conStr => base.MakeConnection(Adapter, ConnectionString));
#else
            return base.MakeConnection(Adapter, ConnectionString);
#endif
        }

        /// <summary> Создание экземпляра движка. </summary>
        /// <param name="Adapter"> Используемый адаптер для ADO.NET провайдера. </param>
        /// <param name="Connection"> Соединение с источником данных в терминологии Entity Framework. </param>
        /// <returns> Созданный движок. </returns>
        protected override EFEngine CreateCore(DbProviderAdapter Adapter, EntityConnection Connection) { return new ZOVNPEngine(Adapter, Connection); }

        //TODO: [Максимов Иван] это вообще следует сделать через интерфейс или MEF, избавившись от ссылки на эту и другие сборки
        /// <summary> Создание движка для работы с данными программы. </summary>
        /// <param name="ConnectionString"> Строка соединения с движком. </param>
        /// <param name="NewPassword"> Новый пароль, который требуется указывать только в случае необходимости смены истекшего пароля. </param>
        /// <returns> Созданный движок. </returns>
        /// <exception cref="Fintech.Model.Abstracts.PasswordException"> В случае, если заданный в параметре <paramref name="ConnectionString"/> пароль истек и новый пароль не был задан. </exception>
        public IEngine Load(string ConnectionString, string NewPassword = null)
        {
#if DEBUG
            ConnectionTuner.Instance.Initialize();
#endif
            return (IEngine)base.Create(ConnectionString, NewPassword);
        }

        /// <summary> Выгрузка движка. </summary>
        /// <param name="Engine"> Выгружаемый движок. </param>
        /// <remarks> Выгружаемый движок будет уничтожен. </remarks>
        public void Unload(IEngine Engine)
        {
            if (Engine == null) return;
            var casted = Engine.CheckOnCast<ZOVNPEngine>("Engine");
            casted.Dispose();
        }

        #endregion
    }
}
